﻿using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using NotAClue.Web;
using NotAClue.ComponentModel.DataAnnotations;

namespace NotAClue.Web.DynamicData
{
    public class DefaultAttributesTypeDescriptor : CustomTypeDescriptor
    {
        #region fields
        private static List<String> s_autoSaffoldFalse = new List<String>() 
            { 
                "InsertedBy", 
                "InsertedOn", 
                "UpdatedBy", 
                "UpdatedOn", 
                "DeletedBy", 
                "DeletedOn" 
            };

        //private static Dictionary<String, List<String>> s_autoHideFilters = new Dictionary<String, List<String>>();
        private static Dictionary<String, String> s_autoFilters = new Dictionary<String, String>();
        private static Dictionary<String, Object> s_autoFilterDefaultValues = new Dictionary<String, Object>();
        private static Dictionary<String, List<Attribute>> s_autoColumnAttributes = new Dictionary<String, List<Attribute>>();
        private static Dictionary<String, Attribute> s_autoTableAttributes = new Dictionary<String, Attribute>();
        #endregion

        #region Properties
        private Type Type { get; set; }

        /// <summary>
        /// Sets the fields that a to be automatically hidden.
        /// </summary>
        /// <value>The automatically hidden fields.</value>
        public static List<String> AutoSaffoldFalse
        {
            set { s_autoSaffoldFalse = value; }
        }

        //public static Dictionary<String, List<String>> AutoHideFilters
        //{
        //    set { s_autoHideFilters = value; }
        //}

        public static Dictionary<String, String> AutoFilters
        {
            set { s_autoFilters = value; }
        }

        public static Dictionary<String, Object> AutoFilterDefaultValue
        {
            set { s_autoFilterDefaultValues = value; }
        }

        public static Dictionary<String, List<Attribute>> AutoColumnAttributes
        {
            set { s_autoColumnAttributes = value; }
        }

        public static Dictionary<String, Attribute> AutoTableAttributes
        {
            set { s_autoTableAttributes = value; }
        }
        #endregion

        public DefaultAttributesTypeDescriptor(ICustomTypeDescriptor parent, Type type)
            : base(parent)
        {
            this.Type = type;
        }

        /// <summary>
        /// Returns the collection of attributes for a given table
        /// </summary>
        /// <returns>AttributeCollection</returns>
        public override AttributeCollection GetAttributes()
        {
            AttributeCollection baseAttributes = base.GetAttributes();
            var tableName = base.GetClassName();
            List<Attribute> extraAttributes = new List<Attribute>();

            //TODO: why does this error occur with the code below

            //var columns = GetProperties();
            //// auto add auto attributes
            //if (s_autoTableAttributes.Count > 0)
            //{
            //    foreach (var item in s_autoTableAttributes)
            //    {
            //        // check if table has column
            //        if (columns.Find(item.Key, true) != null && !baseAttributes.Contains(item.Value))
            //            extraAttributes.Add(item.Value);
            //    }
            //}

            // auto add auto attributes
            if (s_autoTableAttributes.Count > 0)
            {
                foreach (var item in s_autoTableAttributes)
                {
                    // check if table has column
                    if (!baseAttributes.Contains(item.Value))
                        extraAttributes.Add(item.Value);
                }
            }

            if (baseAttributes.OfType<DisplayNameAttribute>().FirstOrDefault() == null)
            {
                // generate the display name
                String friendlyDisplayName = tableName.ToTitleFromPascal();

                // add it to the list
                extraAttributes.Add(new DisplayNameAttribute(friendlyDisplayName));
            }

            // only create a new collection if it is necessary
            return AttributeCollection.FromExisting(baseAttributes, extraAttributes.ToArray());
        }

        /// <summary>
        /// Returns a collection of properties (columns) for the type,
        /// each with attributes for that table
        /// </summary>
        /// <returns>PropertyDescriptorCollection</returns>
        public override PropertyDescriptorCollection GetProperties()
        {
            List<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>();

            foreach (PropertyDescriptor propDescriptor in base.GetProperties())
            {
                List<Attribute> newAttributes = new List<Attribute>();

                // Display Name Rules ...
                // If the property doesn't already have a DisplayNameAttribute defined
                // go ahead and auto-generate one based on the property name
                if (!propDescriptor.HasAttribute<DisplayNameAttribute>())
                {
                    // generate the display name
                    String friendlyDisplayName = propDescriptor.Name.ToTitleFromPascal();

                    // add it to the list
                    newAttributes.Add(new DisplayNameAttribute(friendlyDisplayName));
                }

                //// Display Format Rules ...
                //// If the property doesn't already have a DisplayFormatAttribute defined
                //// go ahead and auto-generate one based on the property type
                //if (!propDescriptor.HasAttribute<DisplayFormatAttribute>() && !propDescriptor.HasAttribute<DataTypeAttribute>())
                //{
                //    // get the default format for the property type
                //    String displayFormat = propDescriptor.PropertyType.GetDisplayFormat();

                //    // add it to the list
                //    newAttributes.Add(new DisplayFormatAttribute() { DataFormatString = displayFormat });
                //}

                // auto add auto attributes
                if (s_autoColumnAttributes.Count > 0 && s_autoColumnAttributes.Keys.Contains(propDescriptor.Name))
                {
                    foreach (var attribute in s_autoColumnAttributes[propDescriptor.Name])
                    {
                        newAttributes.Add(attribute);
                    }
                }

                // auto filters
                if (s_autoFilters.Count > 0 && s_autoFilters.Keys.Contains(propDescriptor.Name))
                {
                    var filterUIHint = propDescriptor.GetAttribute<FilterUIHintAttribute>();
                    if (filterUIHint == null && !String.IsNullOrEmpty(s_autoFilters[propDescriptor.Name]))
                        newAttributes.Add(new FilterUIHintAttribute(s_autoFilters[propDescriptor.Name]));

                    var display = propDescriptor.GetAttribute<DisplayAttribute>();
                    if (display != null)
                        display.AutoGenerateFilter = true;
                    else
                        newAttributes.Add(new DisplayAttribute() { AutoGenerateFilter = true });
                }

                // automatic set the default value
                var defaultValue = propDescriptor.GetAttribute<DefaultValueAttribute>();
                if (defaultValue != null && s_autoFilters.Count > 0 && s_autoFilterDefaultValues.Keys.Contains(propDescriptor.Name))
                    newAttributes.Add(new DefaultValueAttribute(s_autoFilterDefaultValues[propDescriptor.Name]));

                //// automatically hide filter
                //if (s_autoHideFilters.Count > 0 && s_autoHideFilters.Keys.Contains(propDescriptor.Name))
                //{
                //    var filterAttribute = propDescriptor.GetAttribute<FilterAttribute>();
                //    if (filterAttribute != null && s_autoHideFilters[propDescriptor.Name].Count > 0)
                //        newAttributes.Add(new FilterAttribute() { Hidden = true, HiddenForRoles = s_autoHideFilters[propDescriptor.Name].ToArray() });
                //    else
                //        newAttributes.Add(new FilterAttribute() { Hidden = true });
                //}

                // automatically scaffold column false
                if (s_autoSaffoldFalse.Contains(propDescriptor.Name) && !propDescriptor.HasAttribute<ScaffoldColumnAttribute>())
                    newAttributes.Add(new ScaffoldColumnAttribute(false));

                propertyDescriptors.Add(new WrappedPropertyDescriptor(propDescriptor, newAttributes.ToArray()));
            }

            // return the descriptor collection
            return new PropertyDescriptorCollection(propertyDescriptors.ToArray(), true);
        }

        private class WrappedPropertyDescriptor : PropertyDescriptor
        {
            private PropertyDescriptor _wrappedPropertyDescriptor;

            public WrappedPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor, Attribute[] attributes)
                : base(wrappedPropertyDescriptor, attributes)
            {
                _wrappedPropertyDescriptor = wrappedPropertyDescriptor;
            }

            public override bool CanResetValue(object component)
            {
                return _wrappedPropertyDescriptor.CanResetValue(component);
            }

            public override Type ComponentType
            {
                get { return _wrappedPropertyDescriptor.ComponentType; }
            }

            public override object GetValue(object component)
            {
                return _wrappedPropertyDescriptor.GetValue(component);
            }

            public override bool IsReadOnly
            {
                get { return _wrappedPropertyDescriptor.IsReadOnly; }
            }

            public override Type PropertyType
            {
                get { return _wrappedPropertyDescriptor.PropertyType; }
            }

            public override void ResetValue(object component)
            {
                _wrappedPropertyDescriptor.ResetValue(component);
            }

            public override void SetValue(object component, object value)
            {
                _wrappedPropertyDescriptor.SetValue(component, value);
            }

            public override bool ShouldSerializeValue(object component)
            {
                return _wrappedPropertyDescriptor.ShouldSerializeValue(component);
            }
        }
    }
}